#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include "tyn_lib.h"

tyn_config_t* load_config(char *config_file_name, config_t *cfg) {
    tyn_config_t *tyn_config = (tyn_config_t *)malloc( sizeof(tyn_config_t) );
    memcpy(tyn_config->file_name, config_file_name, strlen(config_file_name));
    *(tyn_config->file_name + strlen(config_file_name)) = '\0';
    
    struct stat st;
    if(stat(config_file_name, &st)<0){
        tyn_log(stderr, "cannot open %s, %s\n", config_file_name, strerror (errno));
        return TYN_ERROR;
    }
    char *config_file_str = (char *)malloc( st.st_size + 1);
    *(config_file_str + st.st_size) = '\0';
    int fd_config_file = open(config_file_name, O_RDWR, S_IRWXU|S_IRWXG);
    read(fd_config_file, config_file_str, st.st_size);
    tyn_config->file_str = config_file_str;
    tyn_config->file_str_len = st.st_size;
    
    parse_config(tyn_config, cfg);
    
    return tyn_config;
}


int parse_config(tyn_config_t *tyn_config, config_t *cfg) {
    tyn_config->indexes_count = 0;
    tyn_config->indexes = NULL;

    tyn_config_indexer_t *tyn_config_indexer = (tyn_config_indexer_t *)malloc( sizeof(tyn_config_indexer_t) );
    tyn_config_searchd_t *tyn_config_searchd = (tyn_config_searchd_t *)malloc( sizeof(tyn_config_searchd_t) );

    
    //if(!config_read_file(cfg, config_file_name)) {
    if(!config_read_string(cfg, tyn_config->file_str)){
        tyn_log(stderr, "'%s' : can not open!", tyn_config->file_name);
        tyn_log(stderr, "line: %d\n", config_error_line (cfg));
        return TYN_ERROR;
    }
    
    //get indexer configurations    
    config_setting_t *indexer = config_lookup(cfg, "indexer");
    if(!indexer) {
        tyn_log(stderr, "'%s' : 'indexer' not defined!", tyn_config->file_name);
        return TYN_ERROR;
    }
    
    //get dynamic library directory
    if(!config_setting_lookup_string(indexer, "dl_directory", &tyn_config_indexer->dl_directory)) {
        tyn_log(stderr, "'%s' : 'dl_directory' not defined!", tyn_config->file_name);
        return TYN_ERROR;
    }
    
    //get memory limit
    if(!config_setting_lookup_int(indexer, "memory_limit", &tyn_config_indexer->memory_limit)) {
        tyn_log(stderr, "'%s' : 'memory' not defined!", tyn_config->file_name);
        return TYN_ERROR;
    }
    
    //get search daemon configurations    
    config_setting_t *searchd = config_lookup(cfg, "searchd");
    if(!searchd) {
        tyn_log(stderr, "'%s' : 'searchd' not defined!", tyn_config->file_name);
        return TYN_ERROR;
    }
    
    //get indexes configurations group
    config_setting_t *indexes = config_lookup(cfg, "indexes");
    if(!indexes) {
        tyn_log(stderr, "No indexes definition!");
        return TYN_ERROR;
    }

    //get sources configurations group
    config_setting_t *sources = config_lookup(cfg, "sources");
    if(!sources) {
        tyn_log(stderr, "No data sources definition!");
        return TYN_ERROR;
    }
    
    size_t indexes_count = config_setting_length(indexes);
    if(!indexes_count) {
        tyn_log(stderr, "Empty indexes!");
        return TYN_ERROR;
    }
    
    //initialize tyn_config_indexes
    tyn_config_index_t **tyn_config_indexes = (tyn_config_index_t **)malloc( indexes_count * sizeof(tyn_config_index_t *) );
    
    for(int i = 0; i < indexes_count; i++) {
        //get index configurations
        config_setting_t *index = config_setting_get_elem(indexes, i);
        char *index_name = config_setting_name(index);
        uint32_t index_name_hash = tyn_hash32(index_name, strlen(index_name));
        //get path
        char *path;
        if(!config_setting_lookup_string(index, "path", &path)) {
            tyn_log(stderr, "index '%s': 'path' not defined!", index_name);
            return TYN_ERROR;
        }
        
        //get source name defined in index
        char *source_name;
        if(!config_setting_lookup_string(index, "source", &source_name)) {
            tyn_log(stderr, "index '%s': 'source' not defined!", index_name);
            return TYN_ERROR;
        }

        //get source configurations
        config_setting_t *source = config_setting_get_member(sources, source_name);
        if(!source) {
            tyn_log(stderr, "%s definition not found!");
            return TYN_ERROR;
        }
        
        //get reader dynamic library path
        char *reader_path;
        if(!config_setting_lookup_string(source, "reader_path", &reader_path)) {
            tyn_log(stderr, "index '%s': '%s' : reader_path not defined!", index_name, source_name);
            return TYN_ERROR;
        }
        
        //get source dynamlic library configurations
        config_setting_t *reader_params = config_setting_get_member(source, "reader_params");
        if(!reader_params) {
            tyn_log(stderr, "index '%s': '%s' : reader_params not defined, ", index_name, source_name);
        }
        
        //get tokenizer dynamic library path
        char *tokenizer_path;
        if(!config_setting_lookup_string(index, "tokenizer_path", &tokenizer_path)) {
            tyn_log(stderr, "index '%s': 'tokenizer_path' not defined, use default tokenizer!", index_name);
            //return -1;
        }
        
        //get tokenizer dynamic library settings
        config_setting_t *tokenizer_params = config_setting_get_member(index, "tokenizer_params");
        if(!tokenizer_params) {
            tyn_log(stderr, "index '%s': 'tokenizer_params' not defined, use empty parameters", index_name);
        }
        
        config_setting_t *fields = config_setting_get_member(source, "fields");
        if(fields == NULL) {
            tyn_log(stderr, "index '%s': no fields group definition found", index_name);
            return TYN_ERROR;
        }
        if(config_setting_is_list(fields) == CONFIG_FALSE) {
            tyn_log(stderr, "index '%s': invald fields definition", index_name);
            return TYN_ERROR;
        }
        size_t num_fields = config_setting_length(fields);
        if(num_fields == 0) {
            tyn_log(stderr, "index '%s': no fields definition found", index_name);
            return TYN_ERROR;
        }
        size_t num_doc_id32_field = 0;
        size_t num_doc_id64_field = 0;
        size_t num_fulltext_fields = 0;
        size_t num_attr_int8_t_fields = 0;
        size_t num_attr_int16_t_fields = 0;
        size_t num_attr_int32_t_fields = 0;
        size_t num_attr_int64_t_fields = 0;
        size_t num_attr_uint8_t_fields = 0;
        size_t num_attr_uint16_t_fields = 0;
        size_t num_attr_uint32_t_fields = 0;
        size_t num_attr_uint64_t_fields = 0;
        size_t num_attr_float_fields = 0;
        size_t num_attr_double_fields = 0;
        size_t num_attr_timestamp_fields = 0;

        //validate fields
        for(int i = 0; i < num_fields; i++) {
            config_setting_t *field_settings = config_setting_get_elem(fields, i);
            if(config_setting_is_group(field_settings) == CONFIG_FALSE) {
                tyn_log(stderr, "index '%s': %dth field: invalid definition", index_name, i);
                return TYN_ERROR;
            }
            //size_t num_field_settings = config_setting_length(fields);
            if(config_setting_length(field_settings) < 2) {
                tyn_log(stderr, "index '%s': at least DOC_ID field and one other field must be defined", index_name);
                return TYN_ERROR;
            }
            char *field_name;
            char *field_type;
            int field_bits = 0 ;
            int is_unsigned = 0;
            config_setting_lookup_string(field_settings, "name", &field_name);
            config_setting_lookup_string(field_settings, "type", &field_type);
            
            switch(tyn_hash32(field_type, strlen(field_type))) {
                case TYN_DOC_ID_T:
                    config_setting_lookup_int(field_settings, "bits", &field_bits);
                    switch(field_bits) {
                        case 0:
                        case 32:
                            num_doc_id32_field++;
                        break;
                        case 64:
                            num_doc_id64_field++;
                        break;
                        default:
                            tyn_log(stderr, "unallowed bits definition %d, use default 32bit", field_bits);
                            num_doc_id32_field++;
                        break;
                    }
                break;
                case TYN_FULLTEXT_T:
                    num_fulltext_fields++;
                break;
                case TYN_ATTR_INT_T:
                    config_setting_lookup_int(field_settings, "bits", &field_bits);
                    config_setting_lookup_bool(field_settings, "unsigned", &is_unsigned);
                    switch(field_bits) {
                        case 8:
                            if(is_unsigned) {
                                num_attr_uint8_t_fields++;
                            } else {
                                num_attr_int8_t_fields++;
                            }
                        break;
                        case 16:
                            if(is_unsigned) {
                                num_attr_uint16_t_fields++;
                            } else {
                                num_attr_int16_t_fields++;
                            }
                        break;
                        case 0:
                        case 32:
                            if(is_unsigned) {
                                num_attr_uint32_t_fields++;
                            } else {
                                num_attr_int32_t_fields++;
                            }
                        break;
                        case 64:
                            if(is_unsigned) {
                                num_attr_uint64_t_fields++;
                            } else {
                                num_attr_int64_t_fields++;
                            }
                        break;
                        default:
                            tyn_log(stderr, "unallowed bits definition: %d, use default 32 bit");
                            num_attr_int32_t_fields++;
                        break;
                    }
                break;
                case TYN_ATTR_FLOAT_T:
                    num_attr_float_fields++;
                break;
                case TYN_ATTR_DOUBLE_T:
                    num_attr_double_fields++;
                break;
                case TYN_ATTR_TIMESTAMP_T:
                    num_attr_timestamp_fields++;
                break;
                default:
                    
                break;
            }
            if(!((num_doc_id32_field == 1 && num_doc_id64_field == 0) || (num_doc_id32_field == 0 && num_doc_id64_field == 1))) {
                tyn_log(stderr, "index '%s': invalid DOC_ID field definition, there must be one and only one definition", index_name);
                return TYN_ERROR;
            }
        }
        
        char *doc_id_field;
        int doc_id_field_type = 0;
        char **fulltext_fields = (char **)malloc( num_fulltext_fields * sizeof(char *) );
        //char **attr_int_fields = (char **)malloc( num_attr_int_fields * sizeof(char *) );
        char **attr_int8_t_fields = (char **)malloc( num_attr_int8_t_fields * sizeof(char *) );
        char **attr_uint8_t_fields = (char **)malloc( num_attr_uint8_t_fields * sizeof(char *) );
        char **attr_int16_t_fields = (char **)malloc( num_attr_int16_t_fields * sizeof(char *) );
        char **attr_uint16_t_fields = (char **)malloc( num_attr_uint16_t_fields * sizeof(char *) );
        char **attr_int32_t_fields = (char **)malloc( num_attr_int32_t_fields * sizeof(char *) );
        char **attr_uint32_t_fields = (char **)malloc( num_attr_uint32_t_fields * sizeof(char *) );
        char **attr_int64_t_fields = (char **)malloc( num_attr_int64_t_fields * sizeof(char *) );
        char **attr_uint64_t_fields = (char **)malloc( num_attr_uint64_t_fields * sizeof(char *) );
        char **attr_float_fields = (char **)malloc( num_attr_float_fields * sizeof(char *) );
        char **attr_double_fields = (char **)malloc( num_attr_double_fields * sizeof(char *) );
        char **attr_timestamp_fields = (char **)malloc( num_attr_timestamp_fields * sizeof(char *) );
        uint32_t *fulltext_field_hashes = (uint32_t *)malloc( num_fulltext_fields * sizeof(uint32_t) );
        //uint32_t *attr_int_field_hashes =(uint32_t *)malloc( num_attr_int_fields * sizeof(uint32_t) );
        uint32_t *attr_int8_t_field_hashes = (uint32_t *)malloc( num_attr_int8_t_fields * sizeof(uint32_t) );
        uint32_t *attr_uint8_t_field_hashes = (uint32_t *)malloc( num_attr_uint8_t_fields * sizeof(uint32_t) );
        uint32_t *attr_int16_t_field_hashes = (uint32_t *)malloc( num_attr_int16_t_fields * sizeof(uint32_t) );
        uint32_t *attr_uint16_t_field_hashes = (uint32_t *)malloc( num_attr_uint16_t_fields * sizeof(uint32_t) );
        uint32_t *attr_int32_t_field_hashes = (uint32_t *)malloc( num_attr_int32_t_fields * sizeof(uint32_t) );
        uint32_t *attr_uint32_t_field_hashes = (uint32_t *)malloc( num_attr_uint32_t_fields * sizeof(uint32_t) );
        uint32_t *attr_int64_t_field_hashes = (uint32_t *)malloc( num_attr_int64_t_fields * sizeof(uint32_t) );
        uint32_t *attr_uint64_t_field_hashes = (uint32_t *)malloc( num_attr_uint64_t_fields * sizeof(uint32_t) );
        uint32_t *attr_float_field_hashes = (uint32_t *)malloc( num_attr_float_fields * sizeof(uint32_t) );
        uint32_t *attr_double_field_hashes = (uint32_t *)malloc( num_attr_double_fields * sizeof(uint32_t) );
        uint32_t *attr_timestamp_field_hashes = (uint32_t *)malloc( num_attr_timestamp_fields * sizeof(uint32_t) );

        num_fulltext_fields = 0;
        //um_attr_int_fields = 0;
        num_attr_int8_t_fields = 0;
        num_attr_int16_t_fields = 0;
        num_attr_int32_t_fields = 0;
        num_attr_int64_t_fields = 0;
        num_attr_uint8_t_fields = 0;
        num_attr_uint16_t_fields = 0;
        num_attr_uint32_t_fields = 0;
        num_attr_uint64_t_fields = 0;
        num_attr_float_fields = 0;
        num_attr_double_fields = 0;
        num_attr_timestamp_fields = 0;
        for(int i = 0; i < num_fields; i++) {
            config_setting_t *field_settings = config_setting_get_elem(fields, i);
            char *field_name;
            char *field_type;
            int field_bits = 0;
            int is_unsigned = 0;
            config_setting_lookup_string(field_settings, "name", &field_name);
            config_setting_lookup_string(field_settings, "type", &field_type);
            
            switch(tyn_hash32(field_type, strlen(field_type))) {
                case TYN_DOC_ID_T:
                    config_setting_lookup_int(field_settings, "bits", &field_bits);
                    switch(field_bits) {
                        case 64:
                            doc_id_field_type = 64;
                            num_doc_id64_field++;
                        break;
                        case 0:
                        case 32:
                        default:
                            doc_id_field_type = 32;
                            num_doc_id32_field++;
                        break;
                    }
                    doc_id_field = field_name;
                break;
                case TYN_FULLTEXT_T:
                    *(fulltext_fields + num_fulltext_fields) = field_name;
                    *(fulltext_field_hashes + num_fulltext_fields) = tyn_hash32(field_name, strlen(field_name));
                    num_fulltext_fields++;
                break;
                case TYN_ATTR_INT_T:
                    config_setting_lookup_int(field_settings, "bits", &field_bits);
                    config_setting_lookup_bool(field_settings, "unsigned", &is_unsigned);
                    //printf("unsigned: %d\n", is_unsigned);
                    switch(field_bits) {
                        case 8:
                            if(is_unsigned) {
                                *(attr_uint8_t_fields + num_attr_uint8_t_fields) = field_name;
                                *(attr_uint8_t_field_hashes + num_attr_uint8_t_fields) = tyn_hash32(field_name, strlen(field_name));
                                num_attr_uint8_t_fields++;
                            } else {
                                *(attr_int8_t_fields + num_attr_int8_t_fields) = field_name;
                                *(attr_int8_t_field_hashes + num_attr_int8_t_fields) = tyn_hash32(field_name, strlen(field_name));
                                num_attr_int8_t_fields++;
                            }
                        break;
                        case 16:
                            if(is_unsigned) {
                                *(attr_uint16_t_fields + num_attr_uint16_t_fields) = field_name;
                                *(attr_uint16_t_field_hashes + num_attr_uint16_t_fields) = tyn_hash32(field_name, strlen(field_name));
                                num_attr_uint16_t_fields++;
                            } else {
                                *(attr_int16_t_fields + num_attr_int16_t_fields) = field_name;
                                *(attr_int16_t_field_hashes + num_attr_int16_t_fields) = tyn_hash32(field_name, strlen(field_name));
                                num_attr_int16_t_fields++;
                            }
                        break;
                        case 64:
                            if(is_unsigned) {
                                *(attr_uint64_t_fields + num_attr_uint64_t_fields) = field_name;
                                *(attr_uint64_t_field_hashes + num_attr_uint64_t_fields) = tyn_hash32(field_name, strlen(field_name));
                                num_attr_uint64_t_fields++;
                            } else {
                                *(attr_int64_t_fields + num_attr_int64_t_fields) = field_name;
                                *(attr_int64_t_field_hashes + num_attr_int64_t_fields) = tyn_hash32(field_name, strlen(field_name));
                                num_attr_int64_t_fields++;
                            }
                        break;
                        case 0:
                        case 32:
                        default:
                            if(is_unsigned) {
                                *(attr_uint32_t_fields + num_attr_uint32_t_fields) = field_name;
                                *(attr_uint32_t_field_hashes + num_attr_uint32_t_fields) = tyn_hash32(field_name, strlen(field_name));
                                num_attr_uint32_t_fields++;
                            } else {
                                *(attr_int32_t_fields + num_attr_int32_t_fields) = field_name;
                                *(attr_int32_t_field_hashes + num_attr_int32_t_fields) = tyn_hash32(field_name, strlen(field_name));
                                num_attr_int32_t_fields++;
                            }
                        break;
                    }
                break;
                case TYN_ATTR_FLOAT_T:
                    *(attr_float_fields + num_attr_float_fields) = field_name;
                    *(attr_float_field_hashes + num_attr_float_fields) = tyn_hash32(field_name, strlen(field_name));
                    num_attr_float_fields++;
                break;
                case TYN_ATTR_DOUBLE_T:
                    *(attr_double_fields + num_attr_double_fields) = field_name;
                    *(attr_double_field_hashes + num_attr_double_fields) = tyn_hash32(field_name, strlen(field_name));
                    num_attr_double_fields++;
                break;
                case TYN_ATTR_TIMESTAMP_T:
                    *(attr_timestamp_fields + num_attr_timestamp_fields) = field_name;
                    *(attr_timestamp_field_hashes + num_attr_timestamp_fields) = tyn_hash32(field_name, strlen(field_name));
                    num_attr_timestamp_fields++;
                break;
                default:
                    
                break;
            }
        }
        
        //initialize the index config
        tyn_config_index_t *tyn_config_index = (tyn_config_index_t *)malloc( sizeof(tyn_config_index_t) );
        tyn_config_index->index_name = index_name;
        tyn_config_index->index_name_hash = index_name_hash;
        tyn_config_index->source_name = source_name;
        tyn_config_index->path = path;
        tyn_config_index->reader_path = reader_path;
        tyn_config_index->reader_params = reader_params;
        tyn_config_index->tokenizer_path = tokenizer_path;
        tyn_config_index->tokenizer_params = tokenizer_params;
        
        tyn_config_index->doc_id_field = doc_id_field;
        tyn_config_index->doc_id_field_type = doc_id_field_type;
        tyn_config_index->doc_id_field_hash = tyn_hash32(doc_id_field, strlen(doc_id_field));
/*        
        tyn_config_index->num_fulltext_fields = num_fulltext_fields;
        qsort(fulltext_fields, num_fulltext_fields, sizeof(char *), tyn_compare_string_by_hash32);
        tyn_config_index->fulltext_fields = fulltext_fields;
        qsort(fulltext_field_hashes, num_fulltext_fields, sizeof(uint32_t), tyn_compare_uint32_t);
        tyn_config_index->fulltext_field_hashes = fulltext_field_hashes;
*/        
#define TYN_FIELDS_ACCUMULATE(TYPE) \
        tyn_config_index->num_##TYPE##_fields = num_##TYPE##_fields; \
        qsort(TYPE##_fields, num_##TYPE##_fields, sizeof(char *), tyn_compare_string_by_hash32); \
        tyn_config_index->TYPE##_fields = TYPE##_fields; \
        qsort(TYPE##_field_hashes, num_##TYPE##_fields, sizeof(uint32_t), tyn_compare_uint32_t); \
        tyn_config_index->TYPE##_field_hashes = TYPE##_field_hashes; \

        TYN_FIELDS_ACCUMULATE(fulltext)
        TYN_FIELDS_ACCUMULATE(attr_int8_t)
        TYN_FIELDS_ACCUMULATE(attr_uint8_t)
        TYN_FIELDS_ACCUMULATE(attr_int16_t)
        TYN_FIELDS_ACCUMULATE(attr_uint16_t)
        TYN_FIELDS_ACCUMULATE(attr_int32_t)
        TYN_FIELDS_ACCUMULATE(attr_uint32_t)
        TYN_FIELDS_ACCUMULATE(attr_int64_t)
        TYN_FIELDS_ACCUMULATE(attr_uint64_t)
        TYN_FIELDS_ACCUMULATE(attr_float)
        TYN_FIELDS_ACCUMULATE(attr_double)
        TYN_FIELDS_ACCUMULATE(attr_timestamp)
/*        
        tyn_config_index->num_attr_int8_t_fields = num_attr_int8_t_fields;
        qsort(attr_int8_t_fields, num_attr_int8_t_fields, sizeof(char *), tyn_compare_string_by_hash32);
        tyn_config_index->attr_int8_t_fields = attr_int8_t_fields;
        qsort(attr_int8_t_field_hashes, num_attr_int8_t_fields, sizeof(uint32_t), tyn_compare_uint32_t);
        tyn_config_index->attr_int8_t_field_hashes = attr_int8_t_field_hashes;

        tyn_config_index->num_attr_int16_t_fields = num_attr_int16_t_fields;
        qsort(attr_int16_t_fields, num_attr_int16_t_fields, sizeof(char *), tyn_compare_string_by_hash32);
        tyn_config_index->attr_int16_t_fields = attr_int16_t_fields;
        qsort(attr_int16_t_field_hashes, num_attr_int16_t_fields, sizeof(uint32_t), tyn_compare_uint32_t);
        tyn_config_index->attr_int16_t_field_hashes = attr_int16_t_field_hashes;

        tyn_config_index->num_attr_int32_t_fields = num_attr_int32_t_fields;
        qsort(attr_int32_t_fields, num_attr_int32_t_fields, sizeof(char *), tyn_compare_string_by_hash32);
        tyn_config_index->attr_int32_t_fields = attr_int32_t_fields;
        qsort(attr_int32_t_field_hashes, num_attr_int32_t_fields, sizeof(uint32_t), tyn_compare_uint32_t);
        tyn_config_index->attr_int32_t_field_hashes = attr_int32_t_field_hashes;

        tyn_config_index->num_attr_int64_t_fields = num_attr_int64_t_fields;
        qsort(attr_int64_t_fields, num_attr_int64_t_fields, sizeof(char *), tyn_compare_string_by_hash32);
        tyn_config_index->attr_int64_t_fields = attr_int64_t_fields;
        qsort(attr_int64_t_field_hashes, num_attr_int64_t_fields, sizeof(uint32_t), tyn_compare_uint32_t);
        tyn_config_index->attr_int64_t_field_hashes = attr_int64_t_field_hashes;

        tyn_config_index->num_attr_float_fields = num_attr_float_fields;
        qsort(attr_float_fields, num_attr_float_fields, sizeof(char *), tyn_compare_string_by_hash32);
        tyn_config_index->attr_float_fields = attr_float_fields;
        qsort(attr_float_field_hashes, num_attr_float_fields, sizeof(uint32_t), tyn_compare_uint32_t);
        tyn_config_index->attr_float_field_hashes = attr_float_field_hashes;

        tyn_config_index->num_attr_timestamp_fields = num_attr_timestamp_fields;
        qsort(attr_timestamp_fields, num_attr_timestamp_fields, sizeof(char *), tyn_compare_string_by_hash32);
        tyn_config_index->attr_timestamp_fields = attr_timestamp_fields;
        qsort(attr_timestamp_field_hashes, num_attr_timestamp_fields, sizeof(uint32_t), tyn_compare_uint32_t);
        tyn_config_index->attr_timestamp_field_hashes = attr_timestamp_field_hashes;
*/
        tyn_config_index->num_fields = num_fields;
        *(tyn_config_indexes + i) = tyn_config_index;
    }


    tyn_config->indexer = tyn_config_indexer;
    tyn_config->searchd = tyn_config_searchd;
    tyn_config->indexes_count = indexes_count;
    tyn_config->indexes = tyn_config_indexes;
  
  //return tyn_config;

}

